#include <string.h>
#include <sys/param.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "lwip/err.h"
#include "lwip/sockets.h"

#include "RCS_socket.h"
#include "my_wifi.h"

#define HOST_IPV4_ADDR      "36.138.180.186"
#define HOST_PORT           30091


#define TEST_HOST_IPV4_ADDR      "39.108.140.215"
#define TEST_HOST_PORT           8091

// #define HOST_IPV4_ADDR      "192.168.137.1"
// #define HOST_PORT           8080

#define RETRY_WAIT_NET_UP 10

typedef enum socket_state_machine_e {
  socket_wait_net_up,
  socket_create,
  socket_connect,
  socket_recv,
  socket_close,
}socket_state_machine_t;

socket_state_machine_t socket_thread_state = socket_wait_net_up;

static const char *TAG = "socket";

// success - 0
uint8_t get_net_state(void) {
  wifi_state_t result = get_wifi_state();
  if (result == connect_success_wifi) {
    return 0;
  } else {
    return 1;
  }
  return 1;
}

uint8_t wait_net_up(uint32_t overtime) {
  wifi_state_t result = wifi_wait_result((_BIT(connect_failed_wifi) | _BIT(connect_success_wifi)), 
                                          pdTICKS_TO_MS(overtime));
  if (result == connect_success_wifi) {
    return 0;
  } else {
    return 1;
  }
  return 1;
}

static rcs_socket_t rcs_socket;
static void tcp_client_task(void *pvParameters)
{
  uint8_t rx_buffer[512];
  char host_ip[] = HOST_IPV4_ADDR;
  int addr_family = 0;
  int ip_protocol = 0;

  struct sockaddr_in dest_addr;
  dest_addr.sin_addr.s_addr = inet_addr(host_ip);
  dest_addr.sin_family = AF_INET;
  dest_addr.sin_port = htons(HOST_PORT);
  addr_family = AF_INET;
  ip_protocol = IPPROTO_IP;

  uint8_t socket_thread_flag = 1;
  while (socket_thread_flag) {
    switch (socket_thread_state)
    {
    case socket_wait_net_up: {
      uint8_t ret = wait_net_up(10);
      if (ret == 0) {
        ESP_LOGE(TAG, " wait net connect success, goto create socket");
        socket_thread_state = socket_create;
      } else {
        vTaskDelay(pdMS_TO_TICKS(2000));
      }
      break;
    }
    case socket_create: {
      rcs_socket.sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
      if (rcs_socket.sock < 0) {
        ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
        ESP_LOGE(TAG, "goto close socket");
        socket_thread_state = socket_close;
      } else {
        ESP_LOGE(TAG, " create socket:%d success, goto connect", rcs_socket.sock);
        socket_thread_state = socket_connect;
      }
      break;
    }
    case socket_connect: {
      int err = connect(rcs_socket.sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
      if (err != 0) {
        ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
        ESP_LOGE(TAG, "goto close socket");
        socket_thread_state = socket_close;
      } else {
        ESP_LOGE(TAG, "Successfully connected, goto recv");
        socket_thread_state = socket_recv;
        if (rcs_socket.net_up_cb) {
          rcs_socket.net_up_cb(1);
        }
      }
      break;
    }
    case socket_recv: {
      int len = recv(rcs_socket.sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
      // Error occurred during receiving
      if (len < 0) {
        ESP_LOGE(TAG, "net closed, goto close socket");
        socket_thread_state = socket_close;
      } else {
        // Data received
        rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
        ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
        rcs_socket.inform_cb(rx_buffer, len);
      }
      break;
    }
    case socket_close: {
      if (rcs_socket.sock != -1) {
        ESP_LOGE(TAG, "Shutting down socket and restarting...");
        shutdown(rcs_socket.sock, 0);
        close(rcs_socket.sock);
        if (rcs_socket.net_down_cb)
        {
          rcs_socket.net_down_cb(0);
        }
        ESP_LOGE(TAG, " close socket:%d success", rcs_socket.sock);
        if (get_net_state() == 0) {
          ESP_LOGE(TAG, "net is up, goto socket create");
          socket_thread_state = socket_create;
        } else {
          ESP_LOGE(TAG, "net is down, goto wait net up");
          socket_thread_state = socket_wait_net_up;
        }        
      } else {
        ESP_LOGE(TAG, "socket is -1, stop thread");
        socket_thread_flag = 0;
      }
      vTaskDelay(pdMS_TO_TICKS(1000));
      break;
    }
    default:
      break;
    }
  }
  ESP_LOGE(TAG, "vTaskDelete tcp_client_task");
  vTaskDelete(NULL);
}

void rcs_socket_send(uint8_t *package, size_t raw_len){
  int err = send(rcs_socket.sock, package, raw_len, 0);
  if (err < 0) {
    ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
  }
}

void socket_init(socket_inform_cb_t _inform_cb, socket_up_cb_t _up_cb, socket_down_cb_t _down_cb)
{
  rcs_socket.inform_cb = _inform_cb;
  rcs_socket.net_up_cb = _up_cb;
  rcs_socket.net_down_cb = _down_cb;
  xTaskCreate(tcp_client_task, "tcp_client", 4096, NULL, 5, NULL);
}
